Helidon with Micronaut Data Repositories
Until now, Helidon has followed the Jakarta and MicroProfile (MP) specifications in Helidon MP, and used JPA as our primary choice for database access. Nevertheless, we understand there are other approaches to accessing a database, such as Micronaut's repositories.
We recognize the advantages of using Micronaut’s repositories and its benefits to users, so starting with version 2.2.0 Helidon will support this approach.
TL;DR:
helidon-integrations-micronaut-cdi
allows usage of Micronaut beans in a CDI environment of Helidonhelidon-integrations-micronatu-data
adds support for injection of JDBCConnection
- the integration is one way — you can inject Micronaut beans into CDI beans, you can NOT inject CDI beans into Micronaut beans
- you still need to use annotation processors for Micronaut beans, to be picked up by runtime
- we have tested support for Micronaut data and Micronaut's bean validation implementation
This article considers the usage of Maven as a build tool, and all configuration is Maven-centric. The same could be achieved using Gradle, yet I will leave that to others to explore.
To create a simple microservice with database access using Helidon MP, we need at least the following:
Helidon Parent
The application’s parent pom defines dependency management and plugin management to simplify setup of a new project (you can also use your own parent module, though that requires more configuration and is not the subject of this article). The easiest way to achieve this is to use a Maven archetype, or Helidon CLI (Command Line Interface).
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-mp</artifactId>
<version>2.2.0</version>
</parent>
Helidon and Jakarta Modules
The MicroProfile bundle adds CDI and all required MP specifications. The second library is the integration layer to support Micronaut in CDI. Persistence API is used to annotate an entity (and is provided by Micronaut data).
<dependency>
<groupId>io.helidon.microprofile.bundles</groupId>
<artifactId>helidon-microprofile</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.micronaut</groupId>
<artifactId>helidon-integrations-micronaut-data</artifactId>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<scope>provided</scope>
</dependency>
Database Drivers
I use an in-memory h2 database in this article, though any supported library can be used.
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Micronaut Modules
We will use Micronaut Data to create a repository, and runtime to set up a startup listener to populate the database. The Hikari data source is one of the choices for connection pooling.
<dependency>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-runtime</artifactId>
</dependency>
<dependency>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-tx</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.micronaut.sql</groupId>
<artifactId>micronaut-jdbc-hikari</artifactId>
<scope>runtime</scope>
</dependency>
This next step is Micronaut-specific. Because Micronaut uses a build time approach to analyzing beans and preparing their metadata, we need to configure a set of annotation processors to have required classes generated for runtime.
The Maven compiler plugin should be configured as follows in the build/plugins section (note that the properties are inherited from Helidon parent):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<annotationProcessorPaths>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-inject-java</artifactId>
<version>${version.lib.micronaut}</version>
</path>
<path>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-validation</artifactId>
<version>${version.lib.micronaut}</version>
</path>
<path>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-processor</artifactId>
<version>${version.lib.micronaut.data}</version>
</path>
<path>
<groupId>io.helidon.integrations.micronaut</groupId>
<artifactId>helidon-integrations-micronaut-cdi-processor</artifactId>
<version>${helidon.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Now that we have a setup that contains all necessary libraries, we can build our microservice!
I will start with configuration — we need the following files from src/main/resources/META-INF
: the beans.xml
that defines our module as a bean archive for CDI, and microprofile-config.properties
to configure our service.
To configure the database and server, let's update the configuration file. As you can see, we can configure both Helidon and Micronaut data using the same configuration file. In this article, I use an in-memory database, to simplify required setup.
server.port=8080datasources.default.url=jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
datasources.default.driverClassName=org.h2.Driver
datasources.default.username=sa
datasources.default.password=
datasources.default.schema-generate=CREATE_DROP
datasources.default.dialect=h2
Now let's create a simple entity (refer to Micronaut Data documentation for more information on entities.) This is a simple Owner
table, using Jakarta Persistence API and Micronaut annotation to mark the constructor.
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import io.micronaut.core.annotation.Creator;@Entity
public class Owner {
@Id
@GeneratedValue
private Long id;
private String name;
private int age;
@Creator
public Owner(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
To use this entity in database operation, we need to create a repository. The following is a repository with an equivalent of SELECT * FROM OWNER
and SELECT * FROM OWNER WHERE NAME = ?
:
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;
@JdbcRepository(dialect = Dialect.H2)
public interface DbOwnerRepository extends CrudRepository<Owner, Long> {
/**
* Get all owners from the database.
*
* @return all owners
*/
@Override
List<Owner> findAll();
/**
* Find an owner by name.
*
* @param name name of owner
* @return owner if found
*/
Optional<Owner> findByName(String name);
}
And the last Micronaut-specific step is to initialize the database with data, using a startup event listener:
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.core.annotation.TypeHint;
import io.micronaut.runtime.event.annotation.EventListener;
@Singleton
@TypeHint(typeNames = {"org.h2.Driver", "org.h2.mvstore.db.MVTableEngine"})
public class DbPopulateData {
private final DbOwnerRepository ownerRepository; @Inject
DbPopulateData(DbOwnerRepository ownerRepository) {
this.ownerRepository = ownerRepository;
}
@EventListener
void init(StartupEvent event) {
Owner fred = new Owner("Fred");
fred.setAge(45);
Owner barney = new Owner("Barney");
barney.setAge(40);
ownerRepository.saveAll(Arrays.asList(fred, barney));
}
}
Now let's return to Helidon and create a REST endpoint using JAX-RS. The endpoint is a CDI bean, and thanks to the integration, we can inject Micronaut beans into CDI beans, allowing us to inject the owner repository into our JAX-RS resource:
import javax.inject.Inject;
import javax.validation.constraints.Pattern;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@Path("/owners")
public class OwnerResource {
private final DbOwnerRepository ownerRepository;
@Inject
public OwnerResource(DbOwnerRepository ownerRepo) {
this.ownerRepository = ownerRepo;
}
/**
* Gets all owners from the database.
* @return all owners, using JSON-B to map them to JSON
*/
@GET
public Iterable<Owner> getAll() {
return ownerRepository.findAll();
}
@Path("/{name}")
@GET
public Owner owner(@PathParam("name") String name) {
return ownerRepository.findByName(name)
.orElseThrow(() -> new NotFoundException("Owner by name " + name + " does not exist"));
}
}
And as simple as that, we are now using a Micronaut Data repository from a CDI bean in Helidon!